package com.milloc.db.service.impl;

import com.milloc.db.common.ParameterType;
import com.milloc.db.entity.ParameterEntity;
import com.milloc.db.entity.SQLEntity;
import com.milloc.db.entity.ScriptEntity;
import com.milloc.db.service.ParameterService;
import com.milloc.db.service.SQLRunnerService;
import com.milloc.db.service.ScriptRunnerService;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.stream.Collectors.toList;

/**
 * @author gongdeming
 * @create 2019-08-30
 */
@Service
public class ParameterServiceImpl implements ParameterService {
    private final ScriptRunnerService scriptRunnerService;
    private final SQLRunnerService sqlRunnerService;

    public ParameterServiceImpl(ScriptRunnerService scriptRunnerService, SQLRunnerService sqlRunnerService) {
        this.scriptRunnerService = scriptRunnerService;
        this.sqlRunnerService = sqlRunnerService;
    }

    @Override
    public List<ParameterEntity> load(SQLEntity sqlEntity) {
        if (StringUtils.isBlank(sqlEntity.getSql())) {
            return Collections.emptyList();
        }
        // 用于记录可能的表达式 比如 a.b a
        Set<String> parameters = new HashSet<>();
        // 获取参数型
        final Pattern pattern = Pattern.compile("([#$])\\{(\\w+)([^}]*?)}");
        Matcher matcher = pattern.matcher(sqlEntity.getSql());
        while (matcher.find()) {
            String p = matcher.group(2);
            parameters.add(p);
        }
        // 获取动态sql型
        // 转化成xml
        String xml = "<param>" + sqlEntity.getSql() + "</param>";
        XPathParser xPathParser = new XPathParser(xml);
        // if 和 choose when
        List<XNode> testParentNodes = new ArrayList<>();
        testParentNodes.addAll(xPathParser.evalNodes("/param/if"));
        testParentNodes.addAll(xPathParser.evalNodes("/param/choose/when"));
        // test
        final Pattern testPattern = Pattern.compile("[\\w._]+");
        for (XNode parentNodes : testParentNodes) {
            String test = parentNodes.evalString("@test");
            if (StringUtils.isNotBlank(test)) {
                String[] exp = test.split("\\s+(and|or)\\s+");
                for (String s : exp) {
                    if (StringUtils.isNotBlank(s)) {
                        Matcher m = testPattern.matcher(s);
                        while (m.find()) {
                            parameters.add(m.group());
                        }
                    }
                }
            }
        }
        // foreach
        List<XNode> foreachNodes = xPathParser.evalNodes("/param/foreach");
        for (XNode foreachNode : foreachNodes) {
            String s = foreachNode.evalString("@collection");
            parameters.add(s);
        }
        final String notAWord = "(0x|x|0)?[\\d_]+(\\.[\\d_]+)?[fFlL]?|true|false|null|'[^']*?'|\"[^\"]*?\"";
        List<ParameterEntity> res = parameters.stream()
                .filter(StringUtils::isNotBlank)
                .filter(s -> !s.matches(notAWord))
                .map(s -> s.contains(".") ? StringUtils.substringBefore(s, ".") + "." : s)
                .distinct()
                .sorted()
                .map(s -> {
                    ParameterEntity e = ParameterEntity.of(StringUtils.substringBefore(s, "."));
                    if (s.contains(".")) {
                        e.setType(ParameterType.OBJECT);
                    }
                    return e;
                }).collect(toList());

        if (sqlEntity.isPage()) {
            res.add(new ParameterEntity().setName("pageSize").setType(ParameterType.NUMBER));
            res.add(new ParameterEntity().setName("pageNum").setType(ParameterType.NUMBER));
        }
        return res;
    }

    @Override
    public List<ParameterEntity> load(ScriptEntity scriptEntity) {
        return scriptRunnerService.loadParameter(scriptEntity);
    }
}
